package com.wk.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.wk.search.entity.Goods;
import com.wk.search.entity.SearchParam;
import com.wk.search.service.SearchService;
import com.wk.search.vo.SearchResponseAttrVO;
import com.wk.search.vo.SearchResponseVO;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 搜索
 */
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient restClient;

    //构建查询条件
    @Override
    public SearchResponseVO search(SearchParam param) {
        try {
            SearchResponse response = restClient.search(buildQuery(param), RequestOptions.DEFAULT);
            SearchResponseVO responseVO = parseSearchResult(response);
            responseVO.setPageNum(param.getPageNum());
            responseVO.setPageSize(param.getPageSize());
            return responseVO;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    //解析搜索结果
    private SearchResponseVO parseSearchResult(SearchResponse response) {
        SearchResponseVO responseVO = new SearchResponseVO();

        //获取命中的数据信息
        SearchHits hits = response.getHits();

        //总命中条数
        responseVO.setTotal(hits.totalHits);

        //获取聚合结果集 key:聚合名称,value:聚合内容
        Map<String, Aggregation> aggregationMap = response.getAggregations().asMap();

        //解析品牌的聚合结果集
        parseBrandAgg(response, responseVO, aggregationMap);

        //解析分类聚合结果集
        parseCategoryAgg(responseVO, aggregationMap);

        //解析商品
        parseGoodsAgg(responseVO, hits);

        //解析商品规格属性嵌套聚合对象
        parseAttrsAgg(responseVO, aggregationMap);

        return responseVO;
    }

//----------------------------------------解析查询结果开始--------------------------------------------------


    //解析商品规格属性嵌套聚合对象
    private void parseAttrsAgg(SearchResponseVO responseVO, Map<String, Aggregation> aggregationMap) {
        ParsedNested attrAgg = (ParsedNested)aggregationMap.get("attrAgg");
        //规格参数ID聚合对象
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<? extends Terms.Bucket> buckets = attrIdAgg.getBuckets();

        if (CollectionUtils.isNotEmpty(buckets)) {

            List<SearchResponseAttrVO> attrs = buckets.stream().map(bucket -> {
                SearchResponseAttrVO responseAttrVO = new SearchResponseAttrVO();

                //设置规格参数ID
                responseAttrVO.setProductAttributeId(bucket.getKeyAsNumber().longValue());

                //通过子聚合获取规格参数名称
                List<? extends Terms.Bucket> nameBuckets = ((ParsedStringTerms) bucket.getAggregations().get("attrNameAgg")).getBuckets();
                //id聚合结果对应的name只有一个
                responseAttrVO.setName(nameBuckets.get(0).getKeyAsString());

                //通过子聚合获取规格参数值
                List<? extends Terms.Bucket> valueBuckets = ((ParsedStringTerms) bucket.getAggregations().get("attrValueAgg")).getBuckets();
                //规格值有多个
                List<String> values = valueBuckets.stream().map(value -> value.getKeyAsString()).collect(Collectors.toList());
                responseAttrVO.setValue(values);

                return responseAttrVO;
            }).collect(Collectors.toList());

            responseVO.setAttrs(attrs);
        }
    }


    //解析商品列表
    private void parseGoodsAgg(SearchResponseVO responseVO, SearchHits hits) {
        SearchHit[] subHits = hits.getHits();
        List<Goods> goodsList = new ArrayList<>();
        for (SearchHit subHit : subHits) {
            Goods goods = JSON.parseObject(subHit.getSourceAsString(), Goods.class);
            /**
             * 设置高亮字段
             * subHist.getHighlightFields()返回一个Map
             * 获取Map中的高亮字段 title
             * 返回数组,获取数组中的第一位就是高亮关键字
             */
            goods.setTitle(subHit.getHighlightFields().get("title").fragments()[0].toString());
            goodsList.add(goods);
        }
        responseVO.setProducts(goodsList);
    }


    //解析分类聚合结果集
    private void parseCategoryAgg(SearchResponseVO responseVO, Map<String, Aggregation> aggregationMap) {
        SearchResponseAttrVO category = new SearchResponseAttrVO();
        category.setName("分类");

        //分类ID聚合结果集
        ParsedLongTerms categoryIdAgg = (ParsedLongTerms) aggregationMap.get("categoryIdAgg");

        //从聚合桶中获取聚合数据
        List<String> categoryValues = categoryIdAgg.getBuckets().stream().map(bucket -> {
            Map<String,String> map = new HashMap<>();

            //品牌ID 外层聚合的key
            map.put("id",bucket.getKeyAsString());

            //通过子聚合获取品牌名称
            ParsedStringTerms categoryNameAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("categoryNameAgg");

            //根据品牌ID聚合只有一个名称聚合结果
            String categoryName = categoryNameAgg.getBuckets().get(0).getKeyAsString();
            map.put("name", categoryName);

            return JSON.toJSONString(map);
        }).collect(Collectors.toList());

        category.setValue(categoryValues);
        responseVO.setCatelog(category);
    }


    //解析品牌的聚合结果集
    private void parseBrandAgg(SearchResponse response, SearchResponseVO responseVO, Map<String, Aggregation> aggregationMap) {
        SearchResponseAttrVO brand = new SearchResponseAttrVO();
        brand.setName("品牌");

        //品牌ID聚合结果集 将品牌聚合结果转成map，根据key获取品牌值 key:聚合名称,value:聚合内容
        ParsedLongTerms brandIdAgg = (ParsedLongTerms)aggregationMap.get("brandIdAgg");

        //从聚合桶中获取聚合数据  bucket -> map -> json ->list
        List<String> brandValues = brandIdAgg.getBuckets().stream().map(bucket->{
            Map<String,String> brandMap = new HashMap<>();

            //品牌ID 外层聚合的key
            brandMap.put("id", bucket.getKeyAsString());

            //通过子聚合获取品牌名称
            ParsedStringTerms brandNameAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("brandNameAgg");

            //根据品牌ID聚合只有一个名称聚合结果
            String brandValue = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brandMap.put("name", brandValue);

            //序列化为json字符串
            return JSON.toJSONString(brandMap);
        }).collect(Collectors.toList());

        brand.setValue(brandValues);
        responseVO.setBrand(brand);
    }

//----------------------------------------解析查询结果结束--------------------------------------------------

//----------------------------------------构建查询条件开始--------------------------------------------------

    //构建查询条件
    private SearchRequest buildQuery(SearchParam param) {

        //搜索关键字
        String keyword = param.getKeyword();
        if (StringUtils.isBlank(keyword)) {
            return null;
        }

        //查询条件构建器
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //构建查询条件和过滤条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //关键字、品牌、分类过滤器
        keyWordBrandCategoryFilter(param, keyword, boolQueryBuilder);

        //规格属性的嵌套过滤
        nestedFilter(param, boolQueryBuilder);

        //价格区间过滤
        priceRangeFilter(param, boolQueryBuilder);

        //根据构造条件查询
        sourceBuilder.query(boolQueryBuilder);

        //排序、分页
        pageAndSort(param, sourceBuilder);

        //高亮
        sourceBuilder.highlighter(new HighlightBuilder().field("title").preTags("<em>").postTags("</em>"));

        //品牌、分类、规格属性聚合
        buildAggregations(sourceBuilder);

        //打印DSL语句
        System.out.println(sourceBuilder.toString());

        //结果集过滤,保留需要的字段
        sourceBuilder.fetchSource(new String[]{"skuId","pic","title","price"},null);

        SearchRequest searchRequest = new SearchRequest("goods");       //指定要搜索的索引库
        searchRequest.types("info");        //设置类型
        searchRequest.source(sourceBuilder);
        return searchRequest;
    }

    //价格区间过滤
    private void priceRangeFilter(SearchParam param, BoolQueryBuilder boolQueryBuilder) {
        //价格区间过滤
        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
        Integer priceFrom = param.getPriceFrom();
        Integer priceTo = param.getPriceTo();
        if(priceFrom!=null){
            rangeQuery.gte(priceFrom);
        }

        if(priceTo!=null){
            rangeQuery.lte(priceTo);
        }
        //添加到过滤条件
        boolQueryBuilder.filter(rangeQuery);
    }


    //品牌、分类、规格属性聚合
    private void buildAggregations(SearchSourceBuilder sourceBuilder) {
        //构建品牌聚合 根据brandId聚合 brandName子聚合
        sourceBuilder.aggregation(
                AggregationBuilders
                        .terms("brandIdAgg").field("brandId")
                        .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")));

        //构建分类聚合 根据categoryId聚合 categoryName子聚合
        sourceBuilder.aggregation(
                AggregationBuilders
                        .terms("categoryIdAgg").field("categoryId")
                        .subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));

        //构建规格属性嵌套聚合
        sourceBuilder.aggregation(
                AggregationBuilders.nested("attrAgg","attrs")
                        .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));
    }

    //排序、分页
    private void pageAndSort(SearchParam param, SearchSourceBuilder sourceBuilder) {
        //分页
        Integer pageNum = param.getPageNum();
        Integer pageSize = param.getPageSize();
        sourceBuilder.from((pageNum-1)*pageSize);
        sourceBuilder.size(pageSize);

        //排序  order=1:asc  1：销量  2：价格
        String order = param.getOrder();
        if (StringUtils.isNotBlank(order)) {
            //以":"分割排序条件
            String[] split = StringUtils.split(order, ":");
            if (split != null && split.length == 2) {
                String field = null;
                switch (split[0]){
                    case "1": field = "sale"; break;
                    case "2": field = "price"; break;
                }
                //构建排序
                sourceBuilder.sort(field, SortOrder.valueOf(split[1].toUpperCase()));
            }
        }
    }

    //关键字、品牌、分类过滤器
    private void keyWordBrandCategoryFilter(SearchParam param, String keyword, BoolQueryBuilder boolQueryBuilder) {
        //查询条件  must：多个条件交集;  matchQuery(字段，查询内容);     多个关键词之间是and关系
        boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword).operator(Operator.AND));

        //构建过滤器
        //品牌过滤器
        String[] brands = param.getBrand();
        if(brands!=null && brands.length>0){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",brands));
        }

        //分类过滤器
        String[] catelog3 = param.getCatelog3();
        if(catelog3!=null && catelog3.length>0){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("categoryId",catelog3));
        }
    }


    //构建规格属性的嵌套过滤
    private void nestedFilter(SearchParam param, BoolQueryBuilder boolQueryBuilder) {

        String[] props = param.getProps();
        if(props==null || props.length==0){
            return;
        }
        for (String prop : props) {
            //嵌套查询构建器
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //嵌套查询中的子查询构建器
            BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();

            //规格属性[2:win10-android-ios]以":"隔开
            String[] split = StringUtils.split(prop, ":");
            if (split == null || split.length!=2) {
                continue;
            }

            //下标0是规格ID，下标1是规格值，多个规格属性值以"-"隔开
            String[] attrValues = StringUtils.split(split[1], "-");

            //构建子查询的过滤条件，匹配规格ID和规格值;   ID只有一个，用term；规格值有多个，用terms
            subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
            subBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));

            //嵌套查询放入过滤器中
            boolQuery.must(QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));

            //嵌套过滤条件放入到条件构造其中
            boolQueryBuilder.filter(boolQuery);
        }
    }


}
